; 用来选择截图区域
#Include tipBarGui.ahk
#Include toolBarGui.ahk

class Region extends Gui {

  __New() {
    super.__New('+AlwaysOnTop -Caption +ToolWindow +E0x00080000', Meta.name 'ClipGUI')
    this.thickness := 2
    this.borderPen := Gdip_CreatePen('0xffffad61', this.thickness) ; 边框大小和颜色
    this.staticHdc := GetDC(StaticBG.insId)
    this.Show()
  }

  Destroy() {
    Gdip_DeletePen(this.borderPen)
    ReleaseDC(this.staticHdc)
    super.Destroy()
  }

  static GetRegion() {
    ins := Region(), wList := GetWindowList()
    rect := '', lhwnd := -1

    Hotkey('``', ToggleCursorDisplay, 'On') ; 切换光标捕获

    MouseGetPos(&mx, &my)
    lx := mx, ly := my

    SetTimer HighlightRegion, 10
    Hotkey('LButton', StartClip1, 'On')
    return CancelCB

    CancelCB(*) {
      SetTimer HighlightRegion, 0
      ins.Destroy()
    }

    ToggleCursorDisplay(*) {
      StaticBG.c := !StaticBG.c, StaticBG.ToggleWithCursor()
      if rect
        ins.DrawFrame(rect*)
    }

    HighlightRegion(*) {
      if r := SelectByMousePos(wList)
        rect := r, ins.DrawFrame(rect*)
    }

    StartClip1(*) {
      local mx, my, nx, ny
      SetTimer HighlightRegion, 0
      HotKeysOff(cfg.cancelHK, 'RButton Up') ; 清除右键
      MouseGetPos(&mx, &my)
      while GetKeyState('LButton', 'P') {
        MouseGetPos(&nx, &ny)
        if nx != mx or ny != my
          break
      }
      if mx = nx and my = ny {
        if !rect {
          _x := Max(0, mx - 50), _y := Max(0, my - 50)
          rect := [_x, _y, _x <= 0 ? 100 : Min(A_ScreenWidth - _x, 100), Min(A_ScreenHeight - _y, 100)]
        }
        f := FrameGui()
        f.DrawFrame(rect*)
        ins.Destroy()
      } else {
        ins.Destroy()
        f := GetGui()
      }
      StartClip(f)
    }

    SelectByMousePos(wList, *) {
      local mx, my
      MouseGetPos(&mx, &my)
      if mx = lx and my = ly
        return
      lx := mx, ly := my
      if my = 0 {
        if lhwnd = 0
          return
        lhwnd := 0
        return [1, 1, A_ScreenWidth - ins.thickness, A_ScreenHeight - ins.thickness]
      }
      for v in wList {
        GetWindowRectWithoutShadow(v, &x, &y, &w, &h)
        if mx.between(x, x + w) and my.between(y, y + h) {
          if lhwnd = v
            return
          lhwnd := v
          return [x + 1, y + 1, w - ins.thickness, h - ins.thickness]
        }
      }
    }

    GetWindowList() {
      local x, y, w, h, rects, r
      list := WinGetList(, , "Program Manager")
      list := list.filter(_f)

      rects := Map(), r := []
      for v in list { ; 过滤相同的矩形
        WinGetPos(&x, &y, &w, &h, 'ahk_id' v), key := JoinStr('-', x, y, w, h)
        if !rects.Has(key)
          rects.Set(key, v), r.Push(v)
      }
      return r

      _f(v, i, *) {
        if IsSelfGui(v) ; 自己  TODO 考虑调整为可配置或可切换
          return false
        if WinGetMinMax('ahk_id' v) = -1 ; 最小化
          return false
        WinGetPos(&x, &y, &w, &h, 'ahk_id' v)
        if x > A_ScreenWidth or y > A_ScreenHeight or x + w < 0 or y + h < 0 ; 不在屏幕
          return false
        if x <= 0 and y <= 0 and x + w >= A_ScreenWidth and y + h >= A_ScreenHeight ; 大于屏幕
          return false
        return true
      }
    }
  }

  DrawFrame(x, y, w, h) {
    hdc := CreateCompatibleDC()
    hbm := CreateDIBSection(A_ScreenWidth, A_ScreenHeight)
    obm := SelectObject(hdc, hbm)
    G := Gdip_GraphicsFromHDC(hdc)

    BitBlt(hdc, x, y, w, h, this.staticHdc, x, y)
    Gdip_DrawRectangle(G, this.borderPen, x, y, w, h)

    UpdateLayeredWindow(this.Hwnd, hdc, 0, 0, A_ScreenWidth, A_ScreenHeight)

    SelectObject(hdc, obm)
    DeleteObject(hbm)
    DeleteDC(hdc)
    Gdip_DeleteGraphics(G)
  }
}

StartClip(g) {
  Tip.ShowTip(''), Mask.Show()
  g.Opt('+Owner' (Mask.insId || StaticBG.insId))
  g.GetPos(&x, &y, &w, &h)
  WinActivate(g)
  HotKeysOff('LButton', '``'), Cursor.SetIcon(Cursor.Icon.arrow)
  Hotkey('``', ToggleCursorDisplay, 'On')

  bar := RegionToolBar(Cancel_.Bind(g), Ok_.Bind(g))
  bar.overlay.gui.Opt('+Owner' g.Hwnd)
  bar.Adapt(x, y, w, h).SetTip(w ' x ' h).Refresh()

  me := MouseEvent(g.hwnd)
  me.Register(MouseEvent.Events.LBUTTON_DOWN, ChangeSize)
  me.Register(MouseEvent.Events.MOUSE_WHEEL, ChangeSizeByWheel)
  me.Register(MouseEvent.Events.MOUSE_MOVE, UpdateCursorStyle)
  me.Register(MouseEvent.Events.MOUSE_LEAVE, (*) => Cursor.SetIcon(Cursor.Icon.arrow))

  UpdateCursorStyle(point, *) {
    direction := OverWhere(point)
    switch direction {
      case 0: t := Cursor.Icon.sizeAll
      case 2, 19: t := Cursor.Icon.sizeNS
      case 9, 22: t := Cursor.Icon.sizeNESW
      case 26, 5: t := Cursor.Icon.sizeNWSE
      case 3, 7: t := Cursor.Icon.sizeWE
    }
    Cursor.SetIcon(t)
  }

  ToggleCursorDisplay(*) {
    StaticBG.c := !StaticBG.c, StaticBG.ToggleWithCursor()
    g.Move()
  }

  ChangeSize(point, *) {
    MoveWinAndToolBar(g, bar, OverWhere(point))
  }

  ChangeSizeByWheel(point, up, *) {
    WheelMoveWinAndToolBar(g, bar, up, OverWhere(point.ConvertToClient(g)))
  }

  OverWhere(point) {
    local guiW, guiH, bT, r
    g.GetPos(&_, &__, &guiW, &guiH)
    bT := (guiW > 60 && guiH > 60) ? 25 : 10, r := 0
    if point.x < bT                            ; left
      r += 3
    else if point.x > guiW - bT                ; right
      r += 7
    if point.y < bT                            ; up
      r += 2
    else if point.y > guiH - bT                ; bottom
      r += 19
    return r
  }

  Hotkey cfg.cancelHK, (*) => Cancel_(g), 'On'
  Hotkey 'RButton Up', (*) => Cancel_(g), 'On'
  HotKey 'Left', (*) => HotKeyMoveWinAndToolBar(g, bar, 1), 'On'
  HotKey 'Right', (*) => HotKeyMoveWinAndToolBar(g, bar, -1), 'On'
  HotKey 'Up', (*) => HotKeyMoveWinAndToolBar(g, bar, 2), 'On'
  HotKey 'Down', (*) => HotKeyMoveWinAndToolBar(g, bar, -2), 'On'
  Hotkey '^Left', (*) => TinyAdapter(g, bar, 1), 'On'
  Hotkey '^Right', (*) => TinyAdapter(g, bar, -1), 'On'
  Hotkey '^Up', (*) => TinyAdapter(g, bar, 2), 'On'
  Hotkey '^Down', (*) => TinyAdapter(g, bar, -2), 'On'
  Hotkey '+Left', (*) => TinyAdapter(g, bar, 1, true), 'On'
  Hotkey '+Right', (*) => TinyAdapter(g, bar, -1, true), 'On'
  Hotkey '+Up', (*) => TinyAdapter(g, bar, 2, true), 'On'
  Hotkey '+Down', (*) => TinyAdapter(g, bar, -2, true), 'On'

  Cancel_(g, *) {
    bar.Destroy(), g.Destroy(), ResetState()
  }

  Ok_(g) {
    AutoSave(g.ConvertToPasteGui()), Cancel_(g)
  }
}

GetGui(withTip := true) {
  Hook._Exec(Events.BeforeClip)
  f := FrameGui(), tipbar := TipBarGui()
  MouseGetPos(&begin_x, &begin_y), f.DrawFrame(begin_x, begin_y, 0, 0)
  last_x := 0, last_y := 0
  while GetKeyState('LButton', 'P') {
    MouseGetPos &end_x, &end_y
    x := End_x < Begin_x ? End_x : Begin_x, y := End_y < Begin_y ? End_y : Begin_y
    if last_x = end_x and last_y = end_y
      continue
    scope_x := Abs(begin_x - end_x), scope_y := Abs(begin_y - end_y)
    tipbar.Adapt(x, y).DrawFrame(scope_x ' x ' scope_y)
    last_x := end_x, last_y := end_y
    f.Move(x, y, scope_x, scope_y)
  }
  tipbar.Destroy()
  Hook._Exec(Events.AfterClip)
  return f

  GetRandomColor() => ('0x' Floor(Random(0, 16777216)).toBase(16)).padEnd(8, '0')
}

TinyAdapter(frame, bar, dire, reverse := false) {
  frame.GetPos(&wx, &wy, &ww, &wh)
  if dire = 1 && wx > 0                           ; left
    reverse ? frame.Move(, , ww - 1) : frame.Move(wx - 1, , ww + 1)
  else if dire = -1 && wx + ww < A_ScreenWidth    ; right
    reverse ? frame.Move(wx + 1, , ww - 1) : frame.Move(, , ww + 1)
  else if dire = 2 && wy > 0                      ; up
    reverse ? frame.Move(, , , wh - 1) : frame.Move(, wy - 1, , wh + 1)
  else if dire = -2 && wy + wh < A_ScreenHeight   ; down
    reverse ? frame.Move(, wy + 1, , wh - 1) : frame.Move(, , , wh + 1)
  frame.GetPos(&wx, &wy, &ww, &wh), bar.Adapt(wx, wy, ww, wh).SetTip(Abs(ww) ' x ' Abs(wh)).Refresh()
}

HotKeyMoveWinAndToolBar(frame, bar, dire) {
  frame.GetPos(&wx, &wy, &ww, &wh)
  if dire = 1 && wx > 0                           ; left
    frame.Move(wx - 1)
  else if dire = -1 && wx + ww < A_ScreenWidth    ; right
    frame.Move(wx + 1)
  else if dire = 2 && wy > 0                      ; up
    frame.Move(, wy - 1)
  else if dire = -2 && wy + wh < A_ScreenHeight   ; down
    frame.Move(, wy + 1)
  frame.GetPos(&wx, &wy, &ww, &wh), bar.Adapt(wx, wy, ww, wh).SetTip(Abs(ww) ' x ' Abs(wh)).Refresh()
}

WheelMoveWinAndToolBar(frame, bar, up, dire, *) {
  local x, y, w, h, n := up ? 1 : -1
  frame.GetPos(&x, &y, &w, &h)
  if dire = 0
    up ? frame.Move(x - 1, y - 1, w + 2, h + 2) : frame.Move(x + 1, y + 1, w - 2, h - 2)
  else if dire = 3                            ; left
    up ? frame.Move(x - 1, , w + 1) : frame.Move(x + 1, , w - 1), MouseMove(-n, 0, , 'R')
  else if dire = 7                              ; right
    up ? frame.Move(, , w + 1) : frame.Move(, , w - 1), MouseMove(n, 0, , 'R')
  else if dire = 2                                ; up
    up ? frame.Move(, y - 1, , h + 1) : frame.Move(, y + 1, , h - 1), MouseMove(0, -n, , 'R')
  else if dire = 19                               ; down
    up ? frame.Move(, , , h + 1) : frame.Move(, , , h - 1), MouseMove(0, n, , 'R')
  else if dire = 5                                ; ne
    up ? frame.Move(x - 1, y - 1, w + 1, h + 1) : frame.Move(x + 1, y + 1, w - 1, h - 1), MouseMove(-n, -n, , 'R')
  else if dire = 26                               ; sw
    up ? frame.Move(, , w + 1, h + 1) : frame.Move(, , w - 1, h - 1), MouseMove(n, n, , 'R')
  else if dire = 9                                ; nw
    up ? frame.Move(, y - 1, w + 1, h + 1) : frame.Move(, y + 1, w - 1, h - 1), MouseMove(n, -n, , 'R')
  else if dire = 22                               ; se
    up ? frame.Move(x - 1, , w + 1, h + 1) : frame.Move(x + 1, , w - 1, h - 1), MouseMove(-n, n, , 'R')
  frame.Clamp(), frame.GetPos(&x, &y, &w, &h), bar.Adapt(x, y, w, h).SetTip(Abs(w) ' x ' Abs(h)).Refresh()
}

MoveWinAndToolBar(frame, bar, dire, *) {
  MouseGetPos(&px, &py), frame.GetPos(&wx, &wy, &ww, &wh)
  dx := wx - px, dy := wy - py, MouseGetPos(&lx, &ly)

  While GetKeyState("LButton", "P") {
    MouseGetPos(&nx, &ny)
    if lx = nx and ly = ny
      continue
    if dire = 0 {
      x := nx + dx <= 0 ? 0 : nx + dx
      x := ww + dx + nx >= A_ScreenWidth ? A_ScreenWidth - ww : x
      y := ny + dy <= 0 ? 0 : ny + dy
      y := wh + dy + ny >= A_ScreenHeight ? A_ScreenHeight - wh : y
      frame.Move(x, y)
    } else if dire = 3                             ; left
      frame.Move(nx + dx, , ww + px - nx)
    else if dire = 7                              ; right
      frame.Move(, , ww + nx - px)
    else if dire = 2                                ; up
      frame.Move(, ny + dy, , wh + py - ny)
    else if dire = 19                               ; down
      frame.Move(, , , wh + ny - py)
    else if dire = 5                                ; ne
      frame.Move(nx + dx, ny + dy, ww + px - nx, wh + py - ny)
    else if dire = 26                               ; sw
      frame.Move(, , ww + nx - px, wh + ny - py)
    else if dire = 9                                ; nw
      frame.Move(, ny + dy, ww + nx - px, wh + py - ny)
    else if dire = 22                               ; se
      frame.Move(nx + dx, , ww + px - nx, wh + ny - py)
    lx := nx, ly := ny
    frame.GetPos(&x, &y, &w, &h), bar.Adapt(x, y, w, h).SetTip(Abs(w) ' x ' Abs(h)).Refresh()
  }
  frame.Clamp(), frame.GetPos(&x, &y, &w, &h), bar.Adapt(x, y, w, h).SetTip(Abs(w) ' x ' Abs(h)).Refresh()
}